/*
 * Copyright [2022] [https://www.xiaonuo.vip]
 *
 * Snowy采用APACHE LICENSE 2.0开源协议，您在使用过程中，需要注意以下几点：
 *
 * 1.请不要删除和修改根目录下的LICENSE文件。
 * 2.请不要删除和修改Snowy源码头部的版权声明。
 * 3.本项目代码可免费商业使用，商业使用请保留源码和相关描述文件的项目出处，作者声明等。
 * 4.分发源码时候，请注明软件出处 https://www.xiaonuo.vip
 * 5.不可二次分发开源参与同类竞品，如有想法可联系团队xiaonuobase@qq.com商议合作。
 * 6.若您的项目无法满足以上几点，需要更多功能代码，获取Snowy商业授权许可，请在官网购买授权，地址为 https://www.xiaonuo.vip
 */
package vip.xiaonuo.home.modular.structure.resource.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNode;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import vip.xiaonuo.common.enums.CommonSortOrderEnum;
import vip.xiaonuo.common.exception.CommonException;
import vip.xiaonuo.common.listener.CommonDataChangeEventCenter;
import vip.xiaonuo.common.page.CommonPageRequest;
import vip.xiaonuo.home.core.enums.HomeDataTypeEnum;
import vip.xiaonuo.home.modular.structure.relation.entity.HomeRelation;
import vip.xiaonuo.home.modular.structure.relation.enums.HomeRelationCategoryEnum;
import vip.xiaonuo.home.modular.structure.relation.service.HomeRelationService;
import vip.xiaonuo.home.modular.structure.resource.entity.HomeMenu;
import vip.xiaonuo.home.modular.structure.resource.entity.HomeModule;
import vip.xiaonuo.home.modular.structure.resource.enums.HomeResourceCategoryEnum;
import vip.xiaonuo.home.modular.structure.resource.enums.HomeResourceMenuTypeEnum;
import vip.xiaonuo.home.modular.structure.resource.mapper.HomeMenuMapper;
import vip.xiaonuo.home.modular.structure.resource.param.menu.*;
import vip.xiaonuo.home.modular.structure.resource.service.HomeMenuService;
import vip.xiaonuo.home.modular.structure.resource.service.HomeModuleService;

import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 菜单Service接口实现类
 *
 * @author xuyuxiang
 * @date 2022/6/27 14:25
 **/
@Service
public class HomeMenuServiceImpl extends ServiceImpl<HomeMenuMapper, HomeMenu> implements HomeMenuService {

    @Resource
    private HomeRelationService sysRelationService;

    @Resource
    private HomeModuleService sysModuleService;

    @Override
    public Page<HomeMenu> page(HomeMenuPageParam sysMenuPageParam) {
        QueryWrapper<HomeMenu> queryWrapper = new QueryWrapper<HomeMenu>().checkSqlInjection();
        queryWrapper.lambda().eq(HomeMenu::getCategory, HomeResourceCategoryEnum.MENU.getValue());
        if(ObjectUtil.isNotEmpty(sysMenuPageParam.getSearchKey())) {
            queryWrapper.lambda().like(HomeMenu::getTitle, sysMenuPageParam.getSearchKey());
        }
        if(ObjectUtil.isNotEmpty(sysMenuPageParam.getModule())) {
            queryWrapper.lambda().like(HomeMenu::getModule, sysMenuPageParam.getModule());
        }
        if(ObjectUtil.isAllNotEmpty(sysMenuPageParam.getSortField(), sysMenuPageParam.getSortOrder())) {
            CommonSortOrderEnum.validate(sysMenuPageParam.getSortOrder());
            queryWrapper.orderBy(true, sysMenuPageParam.getSortOrder().equals(CommonSortOrderEnum.ASC.getValue()),
                    StrUtil.toUnderlineCase(sysMenuPageParam.getSortField()));
        } else {
            queryWrapper.lambda().orderByAsc(HomeMenu::getSortCode);
        }
        return this.page(CommonPageRequest.defaultPage(), queryWrapper);
    }

    @Override
    public List<Tree<String>> tree(HomeMenuTreeParam sysMenuTreeParam) {
        LambdaQueryWrapper<HomeMenu> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(HomeMenu::getCategory, HomeResourceCategoryEnum.MENU.getValue())
                .orderByAsc(HomeMenu::getSortCode);
        if(ObjectUtil.isNotEmpty(sysMenuTreeParam.getModule())) {
            lambdaQueryWrapper.eq(HomeMenu::getModule, sysMenuTreeParam.getModule());
        }
        if(ObjectUtil.isNotEmpty(sysMenuTreeParam.getSearchKey())) {
            lambdaQueryWrapper.like(HomeMenu::getTitle, sysMenuTreeParam.getSearchKey());
        }
        List<HomeMenu> resourceList = this.list(lambdaQueryWrapper);

        // 填充上层的父级菜单
        this.fillParentHomeMenuInfo(resourceList);

        List<TreeNode<String>> treeNodeList = resourceList.stream().map(sysMenu ->
                new TreeNode<>(sysMenu.getId(), sysMenu.getParentId(),
                        sysMenu.getTitle(), sysMenu.getSortCode()).setExtra(JSONUtil.parseObj(sysMenu)))
                .collect(Collectors.toList());
        return TreeUtil.build(treeNodeList, "0");
    }

    private void fillParentHomeMenuInfo(List<HomeMenu> resourceList) {
        if(CollUtil.isNotEmpty(resourceList)){
            List<HomeMenu> parentRelationHomeMenus = resourceList.stream().filter(distinctByKey(HomeMenu::getParentId)).collect(Collectors.toList());

            List<String> parentIds = null;
            if(CollUtil.isNotEmpty(parentRelationHomeMenus)){
                parentIds = CollUtil.newArrayList();
                for(HomeMenu parentRelationHomeMenu : parentRelationHomeMenus){
                    if(!StrUtil.equals(parentRelationHomeMenu.getParentId(),"0")){
                        parentIds.add(parentRelationHomeMenu.getParentId());
                    }
                }
            }
            if(CollUtil.isNotEmpty(parentIds)){
                LambdaQueryWrapper<HomeMenu> parentHomeMenuLambdaQueryWrapper = new LambdaQueryWrapper<>();
                parentHomeMenuLambdaQueryWrapper.in(HomeMenu::getId,parentIds);
                List<HomeMenu> parentHomeMenus = this.list(parentHomeMenuLambdaQueryWrapper);
                if(CollUtil.isNotEmpty(parentHomeMenus)){
                    this.fillParentHomeMenuInfo(parentHomeMenus);
                    resourceList.addAll(parentHomeMenus);
                }
            }
        }
    }

    private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Set<Object> seen = ConcurrentHashMap.newKeySet();
        return t -> seen.add(keyExtractor.apply(t));
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void add(HomeMenuAddParam sysMenuAddParam) {
        checkParam(sysMenuAddParam);
        HomeMenu homeMenu = BeanUtil.toBean(sysMenuAddParam, HomeMenu.class);
        boolean repeatTitle = this.count(new LambdaQueryWrapper<HomeMenu>().eq(HomeMenu::getParentId, homeMenu.getParentId())
                .eq(HomeMenu::getCategory, HomeResourceCategoryEnum.MENU.getValue()).eq(HomeMenu::getTitle, homeMenu.getTitle())) > 0;
        if(repeatTitle) {
            throw new CommonException("存在重复的菜单，名称为：{}", homeMenu.getTitle());
        }
        List<HomeMenu> originDataList = this.list(new LambdaQueryWrapper<HomeMenu>().eq(HomeMenu::getCategory,
                HomeResourceCategoryEnum.MENU.getValue()));
        if(!"0".equals(sysMenuAddParam.getParentId())) {
            HomeMenu parentMenu = this.getById(originDataList, sysMenuAddParam.getParentId());
            if(ObjectUtil.isEmpty(parentMenu)) {
                throw new CommonException("上级菜单不存在，id值为：{}", sysMenuAddParam.getParentId());
            }
            if(!parentMenu.getModule().equals(sysMenuAddParam.getModule())) {
                throw new CommonException("module与上级菜单不一致");
            }
        }
        homeMenu.setCategory(HomeResourceCategoryEnum.MENU.getValue());
        this.save(homeMenu);

        // 发布增加事件
        CommonDataChangeEventCenter.doAddWithData(HomeDataTypeEnum.RESOURCE.getValue(), JSONUtil.createArray().put(homeMenu));
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public String addForGenMenu(String parentId, String busName, String title, String module, String path, Integer sortCode) {
        // 参数校验
        if(!"0".equals(parentId)) {
            HomeMenu parentMenu = this.queryEntity(parentId);
            if(ObjectUtil.isEmpty(parentMenu)) {
                throw new CommonException("上级菜单不存在，id值为：{}", parentId);
            }
            if(!parentMenu.getModule().equals(module)) {
                throw new CommonException("module与上级菜单不一致");
            }
        }
        // 删除老菜单（同时删除其下面的菜单、按钮，清除对应的角色与资源信息
        HomeMenu homeOldMenu = this.getOne(new LambdaQueryWrapper<HomeMenu>().eq(HomeMenu::getTitle, title)
                .eq(HomeMenu::getCategory, HomeResourceCategoryEnum.MENU.getValue())
                .eq(HomeMenu::getMenuType, HomeResourceMenuTypeEnum.MENU.getValue()).eq(HomeMenu::getPath, path));
        if(ObjectUtil.isNotEmpty(homeOldMenu)) {
            HomeMenuIdParam sysMenuIdParam = new HomeMenuIdParam();
            sysMenuIdParam.setId(homeOldMenu.getId());
            this.delete(CollectionUtil.newArrayList(sysMenuIdParam));
        }
        // 插入新菜单
        HomeMenu homeMenu = new HomeMenu();
        homeMenu.setParentId(parentId);
        homeMenu.setTitle(title);
        homeMenu.setName(busName);
        homeMenu.setCode(StrUtil.removePrefix(path, StrUtil.SLASH) + StrUtil.SLASH + "page");
        homeMenu.setCategory(HomeResourceCategoryEnum.MENU.getValue());
        homeMenu.setModule(module);
        homeMenu.setMenuType(HomeResourceMenuTypeEnum.MENU.getValue());
        homeMenu.setPath(path);
        homeMenu.setComponent(StrUtil.removePrefix(path, StrUtil.SLASH) + StrUtil.SLASH + "index");
        homeMenu.setIcon("appstore-outlined");
        homeMenu.setSortCode(sortCode);
        this.save(homeMenu);
        return homeMenu.getId();
    }

    private void checkParam(HomeMenuAddParam sysMenuAddParam) {
        HomeResourceMenuTypeEnum.validate(sysMenuAddParam.getMenuType());
        if(sysMenuAddParam.getTitle().contains(StrUtil.DASHED)) {
            throw new CommonException("title不可包含特殊字符【-】");
        }
        if(HomeResourceMenuTypeEnum.MENU.getValue().equals(sysMenuAddParam.getMenuType())) {
            if(ObjectUtil.isEmpty(sysMenuAddParam.getName())) {
                throw new CommonException("name不能为空");
            }
            if(ObjectUtil.isEmpty(sysMenuAddParam.getComponent())) {
                throw new CommonException("component不能为空");
            }
        } else if(HomeResourceMenuTypeEnum.IFRAME.getValue().equals(sysMenuAddParam.getMenuType()) ||
                HomeResourceMenuTypeEnum.LINK.getValue().equals(sysMenuAddParam.getMenuType())) {
            sysMenuAddParam.setName(RandomUtil.randomNumbers(10));
            sysMenuAddParam.setComponent(null);
        } else {
            sysMenuAddParam.setName(null);
            sysMenuAddParam.setComponent(null);
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void edit(HomeMenuEditParam homeMenuEditParam) {
        HomeMenu homeMenu = this.queryEntity(homeMenuEditParam.getId());
        checkParam(homeMenuEditParam);
        BeanUtil.copyProperties(homeMenuEditParam, homeMenu);
        boolean repeatTitle = this.count(new LambdaQueryWrapper<HomeMenu>().eq(HomeMenu::getParentId, homeMenu.getParentId())
                .eq(HomeMenu::getCategory, HomeResourceCategoryEnum.MENU.getValue()).eq(HomeMenu::getTitle, homeMenu.getTitle())
                .ne(HomeMenu::getId, homeMenu.getId())) > 0;
        // 不管是哪个改为菜单，设置组件为空
        if(homeMenuEditParam.getMenuType().equals(HomeResourceMenuTypeEnum.MENU.getValue())) {
            homeMenuEditParam.setComponent(null);
            homeMenuEditParam.setName(null);
        }
        if(repeatTitle) {
            throw new CommonException("存在重复的菜单，名称为：{}", homeMenu.getTitle());
        }
        List<HomeMenu> originDataList = this.list(new LambdaQueryWrapper<HomeMenu>().eq(HomeMenu::getCategory,
                HomeResourceCategoryEnum.MENU.getValue()));
        boolean errorLevel = this.getChildListById(originDataList, homeMenu.getId(), true).stream()
                .map(HomeMenu::getId).collect(Collectors.toList()).contains(homeMenu.getParentId());
        if(errorLevel) {
            throw new CommonException("不可选择上级菜单：{}", this.getById(originDataList, homeMenu.getParentId()).getName());
        }
        if(!"0".equals(homeMenuEditParam.getParentId())) {
            HomeMenu parentMenu = this.getById(originDataList, homeMenuEditParam.getParentId());
            if(ObjectUtil.isEmpty(parentMenu)) {
                throw new CommonException("上级菜单不存在，id值为：{}", homeMenuEditParam.getParentId());
            }
            if(!parentMenu.getModule().equals(homeMenuEditParam.getModule())) {
                throw new CommonException("module与上级菜单不一致");
            }
        }
        this.updateById(homeMenu);

        // 发布更新事件
        CommonDataChangeEventCenter.doUpdateWithData(HomeDataTypeEnum.RESOURCE.getValue(), JSONUtil.createArray().put(homeMenu));
    }

    @Override
    public void changeModule(HomeMenuChangeModuleParam sysMenuChangeModuleParam) {
        HomeMenu sysMenu = this.queryEntity(sysMenuChangeModuleParam.getId());
        if(!"0".equals(sysMenu.getParentId())) {
            throw new CommonException("非顶级菜单不可修改所属模块");
        }
        List<HomeMenu> sysMenuList = this.list(new LambdaQueryWrapper<HomeMenu>().eq(HomeMenu::getCategory,
                HomeResourceCategoryEnum.MENU.getValue()));
        List<HomeMenu> sysMenuChildList = this.getChildListById(sysMenuList, sysMenu.getId(), true).stream()
                .peek(sysMenuTemp -> sysMenuTemp.setModule(sysMenuChangeModuleParam.getModule())).collect(Collectors.toList());
        this.updateBatchById(sysMenuChildList);
    }

    private void checkParam(HomeMenuEditParam sysMenuEditParam) {
        HomeResourceMenuTypeEnum.validate(sysMenuEditParam.getMenuType());
        if(sysMenuEditParam.getTitle().contains(StrUtil.DASHED)) {
            throw new CommonException("title不可包含特殊字符【-】");
        }
        if(HomeResourceMenuTypeEnum.MENU.getValue().equals(sysMenuEditParam.getMenuType())) {
            if(ObjectUtil.isEmpty(sysMenuEditParam.getName())) {
                throw new CommonException("name不能为空");
            }
            if(ObjectUtil.isEmpty(sysMenuEditParam.getComponent())) {
                throw new CommonException("component不能为空");
            }
        } else if(HomeResourceMenuTypeEnum.IFRAME.getValue().equals(sysMenuEditParam.getMenuType()) ||
                HomeResourceMenuTypeEnum.LINK.getValue().equals(sysMenuEditParam.getMenuType())) {
            if(ObjectUtil.isEmpty(sysMenuEditParam.getName())) {
                sysMenuEditParam.setName(RandomUtil.randomNumbers(10));
            }
            sysMenuEditParam.setComponent(null);
        } else {
            sysMenuEditParam.setName(null);
            sysMenuEditParam.setComponent(null);
        }
    }

    @Override
    public void delete(List<HomeMenuIdParam> sysMenuIdParamList) {
        List<String> sysMenuIdList = CollStreamUtil.toList(sysMenuIdParamList, HomeMenuIdParam::getId);
        if(ObjectUtil.isNotEmpty(sysMenuIdList)) {
            // 获取菜单下的菜单、按钮
            List<HomeMenu> allMenuList = this.list(new LambdaQueryWrapper<HomeMenu>()
                    .in(HomeMenu::getCategory, CollectionUtil.newArrayList(HomeResourceCategoryEnum.MENU.getValue(),
                            HomeResourceCategoryEnum.BUTTON.getValue())));
            List<String> toDeleteMenuIdList = CollectionUtil.newArrayList();
            sysMenuIdList.forEach(menuId -> toDeleteMenuIdList.addAll(this.getChildListById(allMenuList, menuId, true).stream()
                    .map(HomeMenu::getId).collect(Collectors.toList())));
            if(ObjectUtil.isNotEmpty(toDeleteMenuIdList)) {
                // 清除对应的角色与资源信息
                sysRelationService.remove(new LambdaUpdateWrapper<HomeRelation>().in(HomeRelation::getTargetId, toDeleteMenuIdList)
                        .eq(HomeRelation::getCategory, HomeRelationCategoryEnum.SYS_ROLE_HAS_RESOURCE.getValue()));
                // 执行删除
                this.removeByIds(toDeleteMenuIdList);

                // 发布删除事件
                CommonDataChangeEventCenter.doDeleteWithDataId(HomeDataTypeEnum.RESOURCE.getValue(), toDeleteMenuIdList);
            }
        }
    }

    @Override
    public HomeMenu detail(HomeMenuIdParam sysMenuIdParam) {
        return this.queryEntity(sysMenuIdParam.getId());
    }

    @Override
    public HomeMenu queryEntity(String id) {
        HomeMenu sysMenu = this.getById(id);
        if(ObjectUtil.isEmpty(sysMenu)) {
            throw new CommonException("菜单不存在，id值为：{}", id);
        }
        return sysMenu;
    }

    @Override
    public List<HomeModule> moduleSelector(HomeMenuSelectorModuleParam sysMenuSelectorModuleParam) {
        LambdaQueryWrapper<HomeModule> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        if(ObjectUtil.isNotEmpty(sysMenuSelectorModuleParam.getSearchKey())) {
            lambdaQueryWrapper.like(HomeModule::getTitle, sysMenuSelectorModuleParam.getSearchKey());
        }
        lambdaQueryWrapper.eq(HomeModule::getCategory, HomeResourceCategoryEnum.MODULE.getValue());
        return sysModuleService.list(lambdaQueryWrapper);
    }

    @Override
    public List<Tree<String>> menuTreeSelector(HomeMenuSelectorMenuParam sysMenuSelectorMenuParam) {
        LambdaQueryWrapper<HomeMenu> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 查询类型为菜单的
        lambdaQueryWrapper.eq(HomeMenu::getCategory, HomeResourceCategoryEnum.MENU.getValue());
        if(ObjectUtil.isNotEmpty(sysMenuSelectorMenuParam.getModule())) {
            lambdaQueryWrapper.eq(HomeMenu::getModule, sysMenuSelectorMenuParam.getModule());
        }
        List<HomeMenu> resourceList = this.list(lambdaQueryWrapper);
        List<TreeNode<String>> treeNodeList = resourceList.stream().map(sysMenu ->
                new TreeNode<>(sysMenu.getId(), sysMenu.getParentId(),
                        sysMenu.getTitle(), sysMenu.getSortCode()).setExtra(JSONUtil.parseObj(sysMenu)))
                .collect(Collectors.toList());
        return TreeUtil.build(treeNodeList, "0");
    }

    /* ====以下为各种递归方法==== */

    @Override
    public List<HomeMenu> getChildListById(List<HomeMenu> originDataList, String id, boolean includeSelf) {
        List<HomeMenu> resultList = CollectionUtil.newArrayList();
        execRecursionFindChild(originDataList, id, resultList);
        if(includeSelf) {
            HomeMenu self = this.getById(originDataList, id);
            if(ObjectUtil.isNotEmpty(self)) {
                resultList.add(self);
            }
        }
        return resultList;
    }

    @Override
    public List<HomeMenu> getParentListById(List<HomeMenu> originDataList, String id, boolean includeSelf) {
        List<HomeMenu> resultList = CollectionUtil.newArrayList();
        execRecursionFindParent(originDataList, id, resultList);
        if(includeSelf) {
            HomeMenu self = this.getById(originDataList, id);
            if(ObjectUtil.isNotEmpty(self)) {
                resultList.add(self);
            }
        }
        return resultList;
    }

    public void execRecursionFindChild(List<HomeMenu> originDataList, String id, List<HomeMenu> resultList) {
        originDataList.forEach(item -> {
            if(item.getParentId().equals(id)) {
                resultList.add(item);
                execRecursionFindChild(originDataList, item.getId(), resultList);
            }
        });
    }

    public void execRecursionFindParent(List<HomeMenu> originDataList, String id, List<HomeMenu> resultList) {
        originDataList.forEach(item -> {
            if(item.getId().equals(id)) {
                HomeMenu parent = this.getById(originDataList, item.getParentId());
                if(ObjectUtil.isNotEmpty(parent)) {
                    resultList.add(parent);
                }
                execRecursionFindParent(originDataList, item.getParentId(), resultList);
            }
        });
    }

    public HomeMenu getById(List<HomeMenu> originDataList, String id) {
        int index = CollStreamUtil.toList(originDataList, HomeMenu::getId).indexOf(id);
        return index == -1?null:originDataList.get(index);
    }

    public HomeMenu getParentById(List<HomeMenu> originDataList, String id) {
        HomeMenu self = this.getById(originDataList, id);
        return ObjectUtil.isNotEmpty(self)?self:this.getById(originDataList, self.getParentId());
    }

    public HomeMenu getChildById(List<HomeMenu> originDataList, String id) {
        int index = CollStreamUtil.toList(originDataList, HomeMenu::getParentId).indexOf(id);
        return index == -1?null:originDataList.get(index);
    }
}
